#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import lxml
import bnetd
import functools
import importlib
from bnet_param import WrtConst


class WrtPluginHub:

    def __init__(self):
        pluginsDir = os.path.join(WrtConst.libDir, "plugins")

        self._pluginDict = {}
        for fn in os.listdir(pluginsDir):
            for m in self._loadPluginMetadata(pluginsDir, fn):
                clazz = m.getClass()
                self._pluginDict[m.getId()] = clazz(m.getId(), m.getDescription())

    def dispose(self):
        for obj in self._pluginDict.values():
            obj.dispose()

    def getPluginListByType(self, pluginType):
        return [x for x in self._pluginDict.values() if x.plugin_type == pluginType]

    def _loadPluginMetadata(self, pluginsDir, pluginName):
        path = os.path.join(pluginsDir, pluginName)

        # get metadata.xml file
        metadata_file = os.path.join(path, "metadata.xml")
        if not os.path.exists(metadata_file):
            raise Exception("plugin %s has no metadata.xml" % (pluginName))
        if not os.path.isfile(metadata_file):
            raise Exception("metadata.xml for plugin %s is not a file" % (pluginName))
        if not os.access(metadata_file, os.R_OK):
            raise Exception("metadata.xml for plugin %s is invalid" % (pluginName))

        # check metadata.xml file content
        # FIXME
        rootElem = lxml.etree.parse(metadata_file).getroot()
        # if True:
        #     dtd = libxml2.parseDTD(None, constants.PATH_PLUGIN_DTD_FILE)
        #     ctxt = libxml2.newValidCtxt()
        #     messages = []
        #     ctxt.setValidityErrorHandler(lambda item, msgs: msgs.append(item), None, messages)
        #     if tree.validateDtd(ctxt, dtd) != 1:
        #         msg = ""
        #         for i in messages:
        #             msg += i
        #         raise exceptions.IncorrectPluginMetaFile(metadata_file, msg)

        if rootElem.tag == "plugins":
            # for example:
            # openvpn has a subnet plugin and a connection plugin
            # these 2 plugins share code, have same dependencies
            # so they should be installed together in one plugin directory
            for child in rootElem.xpath("./plugin"):
                childId = child.get("id")
                yield _PluginMetadata(childId, path, child)
        elif rootElem.tag == "plugin":
            pluginId = rootElem.get("id")
            yield _PluginMetadata(pluginId, path, rootElem)
        else:
            raise Exception("plugin %s has invalid root tag" % (pluginName))


class _PluginMetadata:

    def __init__(self, pluginId, pluginPath, rootElem):
        self._modname = os.path.basename(pluginPath)
        self._id = pluginId
        self._rootElem = rootElem

        self._desc = None
        if True:
            ret = self._rootElem.xpath("./description")
            if len(ret) == 0:
                raise Exception("no description property in plugin \"%s\"" % (self._id))
            if len(ret) > 1:
                raise Exception("multiple description property in plugin \"%s\"" % (self._id))
            self._desc = ret[0].text

        self._className = None
        if True:
            ret = self._rootElem.xpath("./classname")
            if len(ret) == 0:
                raise Exception("no classname property in plugin \"%s\"" % (self._id))
            if len(ret) > 1:
                raise Exception("multiple classname property in plugin \"%s\"" % (self._id))
            self._className = ret[0].text

    def getId(self):
        return self._id

    def getDescription(self):
        return self._desc

    @functools.cache
    def getClass(self):
        return getattr(importlib.import_module(".plugins.%s.%s" % (self._modname, self._className), package=__package__), "Plugin")

